package com.wywah.yunduo.config;

import com.wywah.yunduo.security.core.CustomeOAuth2AccessTokenGenerator;
import com.wywah.yunduo.security.core.CustomeOAuth2TokenCustomizer;
import com.wywah.yunduo.security.handler.token.YunduoAuthenticationFailureHandler;
import com.wywah.yunduo.security.handler.token.YunduoAuthenticationSuccessEventHandler;
import com.wywah.yunduo.security.properties.SecurityPermitUriProperties;
import com.wywah.yunduo.security.properties.YunduoSecurityProperties;
import com.wywah.yunduo.security.supports.RedisOAuth2AuthorizationService;
import com.wywah.yunduo.security.supports.client.YunduoClientSecretAuthenticationProvider;
import com.wywah.yunduo.security.supports.client.YunduoClientSecretBasicAuthenticationConverter;
import com.wywah.yunduo.security.supports.employee.OAuth2EmployeeAuthenticationConverter;
import com.wywah.yunduo.security.supports.employee.OAuth2EmployeeAuthenticationProvider;
import com.wywah.yunduo.security.supports.form.FormLoginConfigurer;
import com.wywah.yunduo.security.supports.password.OAuth2PasswordAuthenticationConverter;
import com.wywah.yunduo.security.supports.password.OAuth2PasswordAuthenticationProvider;
import com.wywah.yunduo.security.supports.password.PasswordUserDetailsAuthenticationProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.Arrays;

@EnableConfigurationProperties({
    SecurityPermitUriProperties.class,
        YunduoSecurityProperties.class
})
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfiguration {

    private final OAuth2AuthorizationService authorizationService;
    private final YunduoSecurityProperties yunduoSecurityProperties;
    private final SecurityPermitUriProperties securityPermitUriProperties;
    private final RegisteredClientRepository registeredClientRepository;

    public AuthorizationServerConfiguration(OAuth2AuthorizationService authorizationService,
                                            YunduoSecurityProperties yunduoSecurityProperties,
                                            SecurityPermitUriProperties securityPermitUriProperties,
                                            RegisteredClientRepository registeredClientRepository) {
        this.authorizationService = authorizationService;
        this.yunduoSecurityProperties = yunduoSecurityProperties;
        this.securityPermitUriProperties = securityPermitUriProperties;
        this.registeredClientRepository = registeredClientRepository;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        http.with(new OAuth2AuthorizationServerConfigurer(), Customizer.withDefaults())
                .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                .csrf(AbstractHttpConfigurer::disable);

        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);
        // 个性化认证授权端点
        authorizationServerConfigurer.tokenEndpoint(tokenEndpoint -> {
            tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter())
                    .accessTokenResponseHandler(new YunduoAuthenticationSuccessEventHandler())
                    .errorResponseHandler(new YunduoAuthenticationFailureHandler());
        });
        // TODO个性化客户端认证
        authorizationServerConfigurer.clientAuthentication(configurer -> {
            configurer.authenticationConverters(converters -> converters.add(new YunduoClientSecretBasicAuthenticationConverter()))
                    .authenticationProviders(providers -> providers.add(new YunduoClientSecretAuthenticationProvider(registeredClientRepository, authorizationService)));
        });


        AntPathRequestMatcher[] requestMatchers = securityPermitUriProperties.getUrls()
                .stream()
                .map(AntPathRequestMatcher::new)
                .toList()
                .toArray(new AntPathRequestMatcher[] {});

        http.authorizeHttpRequests(authorizeRequests -> {
            // 自定义接口、端点暴露
            authorizeRequests.requestMatchers(requestMatchers).permitAll().anyRequest().authenticated();
        });

        authorizationServerConfigurer.authorizationService(authorizationService)
                .authorizationServerSettings(
                        AuthorizationServerSettings.builder().issuer(this.yunduoSecurityProperties.getIssuer()).build());
        http.with(new FormLoginConfigurer(yunduoSecurityProperties), Customizer.withDefaults());
        DefaultSecurityFilterChain securityFilterChain = http.build();
        // 必须在http.build之后注入自定义授权模式实现
        addCustomOAuth2GrantAuthenticationProvider(http);
        return securityFilterChain;
    }

    @Bean
    public AuthenticationConverter accessTokenRequestConverter() {
        return new DelegatingAuthenticationConverter(Arrays.asList(
                new OAuth2PasswordAuthenticationConverter(),
                new OAuth2EmployeeAuthenticationConverter()
        ));
    }

    /**
     * 令牌生成规则实现 </br>
     * client:username:uuid
     * @return OAuth2TokenGenerator
     */
    @Bean
    public OAuth2TokenGenerator<OAuth2Token> oAuth2TokenGenerator() {
        CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator(new CustomeOAuth2TokenCustomizer());
        return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
    }



    private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);

        OAuth2PasswordAuthenticationProvider passwordAuthenticationProvider =
                new OAuth2PasswordAuthenticationProvider(authorizationService,oAuth2TokenGenerator(),authenticationManager);


        // 处理 authenticationProvider生成的 UsernamePasswordAuthenticationToken的授权信息
        http.authenticationProvider(new PasswordUserDetailsAuthenticationProvider());
        // 处理 OAuth2PasswordAuthenticationToken
        http.authenticationProvider(passwordAuthenticationProvider);
        http.authenticationProvider(new OAuth2EmployeeAuthenticationProvider(http));
    }

}
